#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import argparse
import shutil
import os
import subprocess

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()


app_name = "cpp-hello"
app_test_name = app_name + "_test"
# clang-format 处理目录列表
format_src_dirs = ["src", "include", "test"]
# clang-tidy 处理目录列表#
tidy_src_dirs = format_src_dirs
# lcov port
port_lcov = 5001
# doxygen port
port_doxygen = 5002
# 是否打印 shell 命令执行过程
flag_print_shell_command_process = False
# 获取当前脚本所在目录
script_dir = os.path.dirname(os.path.abspath(__file__))
# 构建目录
build_dir = script_dir + os.sep + "build"
# release cmake preset
preset_release = "ninja-release"
# debug cmake preset
preset_debug = "gnu-debug"
# release 构建目录
build_release_dir = build_dir + os.sep + preset_release
# debug 构建目录
build_debug_dir = build_dir + os.sep + preset_debug
# bin 目录
bin_dir = "dist" + os.sep + "bin"
# release 程序所在目录
bin_release_dir = build_release_dir + os.sep + bin_dir
# debug 程序所在目录
bin_debug_dir = build_debug_dir + os.sep + bin_dir
# test 目录
test_dir = "dist" + os.sep + "test"
# test release 程序所在目录
test_release_dir = build_release_dir + os.sep + test_dir


sub_command_clean_release = "clean-release"
sub_command_clean_release_short = "cr"

sub_command_clean_debug = "clean-debug"
sub_command_clean_debug_short = "cd"

sub_command_build_release = "build-release"
sub_command_build_release_short = "br"

sub_command_build_debug = "build-debug"
sub_command_build_debug_short = "bd"

sub_command_rebuild_release = "rebuild-release"
sub_command_rebuild_release_short = "rbr"

sub_command_rebuild_debug = "rebuild-debug"
sub_command_rebuild_debug_short = "rbd"

sub_command_run_release = "run"
sub_command_run_release_short = "r"

sub_command_run_debug = "run-debug"
sub_command_run_debug_short = "rd"

sub_command_rerun_release = "rerun"
sub_command_rerun_release_short = "rr"

sub_command_rerun_debug = "rerun-debug"
sub_command_rerun_debug_short = "rrd"

sub_command_run_test_common = "run-test-common"
sub_command_run_test_common_short = "rtc"

sub_command_rerun_test_common = "rerun-test-common"
sub_command_rerun_test_common_short = "rrtc"

sub_command_coverage = "cov"
sub_command_doc = "doc"
sub_command_format = "format"
sub_command_tidy = "tidy"


def exec_shell_command(command, cwd=script_dir):
    print(command)
    process = subprocess.Popen(
        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
    )
    while True:
        output = process.stdout.readline().decode("utf-8")
        error = process.stderr.readline().decode("utf-8")
        if output == "" and process.poll() is not None:
            break
        if output:
            if flag_print_shell_command_process:
                print(output, end="")
        if error:
            print(error)

    process.wait()

    if 0 != process.returncode:
        print(command + " failed")


def exec_exec(exec_path, args=[], cwd=script_dir):
    process = subprocess.Popen(
        [exec_path] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
    )
    output, error = process.communicate()
    if output:
        print(output.decode("utf-8"))
    if error:
        print(error.decode("utf-8"))
    if 0 != process.returncode:
        print(exec_path + " 执行失败")


def register_cmd_clean_release():
    cmd = subparsers.add_parser(
        sub_command_clean_release,
        aliases=[sub_command_clean_release_short],
        help="清理 release",
    )
    cmd.set_defaults(func=action_clean_release)


def action_clean_release():
    print("clean_release")
    if os.path.exists(build_release_dir):
        shutil.rmtree(build_release_dir)


def register_cmd_clean_debug():
    cmd = subparsers.add_parser(
        sub_command_clean_debug,
        aliases=[sub_command_clean_debug_short],
        help="清理 debug",
    )
    cmd.set_defaults(func=action_clean_debug)


def action_clean_debug():
    print("clean_debug")
    if os.path.exists(build_debug_dir):
        shutil.rmtree(build_debug_dir)


def register_cmd_build_release():
    cmd = subparsers.add_parser(
        sub_command_build_release,
        aliases=[sub_command_build_release_short],
        help="构建 release",
    )
    cmd.set_defaults(func=action_build_release)


def action_build_release():
    print("build_release")
    exec_shell_command("cmake --preset=" + preset_release)
    exec_shell_command("cmake --build --preset=" + preset_release)


def register_cmd_build_debug():
    cmd = subparsers.add_parser(
        sub_command_build_debug,
        aliases=[sub_command_build_debug_short],
        help="构建 debug",
    )
    cmd.set_defaults(func=action_build_debug)


def action_build_debug():
    print("build_debug")
    exec_shell_command("cmake --preset=" + preset_debug)
    exec_shell_command("cmake --build --preset=" + preset_debug)


def register_cmd_rebuild_release():
    cmd = subparsers.add_parser(
        sub_command_rebuild_release,
        aliases=[sub_command_rebuild_release_short],
        help="清理并重新构建 release",
    )
    cmd.set_defaults(func=action_rebuild_release)


def action_rebuild_release():
    print("rebuild_release")
    action_clean_release()
    action_build_release()


def register_cmd_rebuild_debug():
    cmd = subparsers.add_parser(
        sub_command_rebuild_debug,
        aliases=[sub_command_rebuild_debug_short],
        help="清理并重新构建 debug",
    )
    cmd.set_defaults(func=action_rebuild_debug)


def action_rebuild_debug():
    print("rebuild_debug")
    action_clean_debug()
    action_build_debug()


def register_cmd_run_release():
    cmd = subparsers.add_parser(
        sub_command_run_release,
        aliases=[sub_command_run_release_short],
        help="运行 release",
    )
    cmd.set_defaults(func=action_run_release)


def action_run_release():
    action_build_release()
    print("run_release")
    exec_path = bin_release_dir + os.sep + app_name
    print(exec_path)
    exec_exec(exec_path=exec_path, cwd=bin_release_dir)


def register_cmd_run_debug():
    cmd = subparsers.add_parser(
        sub_command_run_debug, aliases=[sub_command_run_debug_short], help="运行 debug"
    )
    cmd.set_defaults(func=action_run_debug)


def action_run_debug():
    action_build_debug()
    print("run_debug")
    exec_path = bin_debug_dir + os.sep + app_name
    print(exec_path)
    exec_exec(exec_path=exec_path, cwd=bin_debug_dir)


def register_cmd_rerun_release():
    cmd = subparsers.add_parser(
        sub_command_rerun_release,
        aliases=[sub_command_rerun_release_short],
        help="清理并重新运行 release",
    )
    cmd.set_defaults(func=action_rerun_release)


def action_rerun_release():
    print("rerun_release")
    action_clean_release()
    action_run_release()


def register_cmd_rerun_debug():
    cmd = subparsers.add_parser(
        sub_command_rerun_debug,
        aliases=[sub_command_rerun_debug_short],
        help="清理并重新运行 debug",
    )
    cmd.set_defaults(func=action_rerun_debug)


def action_rerun_debug():
    print("rerun_debug")
    action_clean_debug()
    action_run_debug()


def register_cmd_run_test_common():
    cmd = subparsers.add_parser(
        sub_command_run_test_common,
        aliases=[sub_command_run_test_common_short],
        help="运行 test_common (release 模式)",
    )
    cmd.set_defaults(func=action_run_test_common)


def action_run_test_common():
    action_build_release()
    print("run_test_common")
    exec_path = test_release_dir + os.sep + app_test_name
    print(exec_path)
    exec_exec(exec_path=exec_path, args=["-tc=test_common"], cwd=test_release_dir)


def register_cmd_rerun_test_common():
    cmd = subparsers.add_parser(
        sub_command_rerun_test_common,
        aliases=[sub_command_rerun_test_common_short],
        help="清理并重新运行 test_common (release 模式)",
    )
    cmd.set_defaults(func=action_rerun_test_common)


def action_rerun_test_common():
    action_clean_release()
    print("rerun_test_common")
    action_run_test_common()


def register_cmd_coverage():
    cmd = subparsers.add_parser(
        sub_command_coverage,
        help="清理并重新运行 gnu-release 并输出测试覆盖率报告",
    )
    cmd.set_defaults(func=action_coverage)


def action_coverage():
    print("coverage")
    cov_preset = "gnu-release-coverage"
    cov_build_dir = build_dir + os.sep + cov_preset
    if os.path.exists(cov_build_dir):
        shutil.rmtree(cov_build_dir)
    exec_shell_command("cmake --preset=" + cov_preset)
    exec_shell_command("cmake --build --preset=" + cov_preset)
    exec_shell_command("ctest --preset=" + cov_preset)
    exec_shell_command(
        f"lcov -d . -o {cov_build_dir}/app.info -b . -c --exclude '*/test/*' --exclude '*/src/main/*'"
        + " --exclude '*/third/*' --exclude '*/build/*' --exclude '/usr/include/*'",
    )
    exec_shell_command(
        f"genhtml {cov_build_dir}/app.info -o {cov_build_dir}/lcov -t '{app_name} 测试覆盖率报告'"
    )
    process = subprocess.Popen(
        f"cd {cov_build_dir}/lcov && serve -p {port_lcov}", shell=True
    )
    process.wait()


def register_cmd_doc():
    cmd = subparsers.add_parser(
        sub_command_doc,
        help="生成 doxygen 文档",
    )
    cmd.set_defaults(func=action_doc)


def action_doc():
    print("doxygen")
    doc_dir = build_dir + os.sep + "doc"
    if os.path.exists(doc_dir):
        shutil.rmtree(doc_dir)
    os.makedirs(doc_dir)
    exec_shell_command("cd doc && doxygen Doxyfile")
    exec_shell_command(
        "cp doc/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js build/doc/html/"
    )
    exec_shell_command(
        "cp doc/doxygen-awesome-css/doxygen-awesome-interactive-toc.js build/doc/html/"
    )
    exec_shell_command("cp -r doc/images build/doc/html/images")
    process = subprocess.Popen(
        f"cd build/doc/html && serve -p {port_doxygen}", shell=True
    )
    process.wait()


def register_cmd_format():
    cmd = subparsers.add_parser(
        sub_command_format,
        help="格式化代码",
    )
    cmd.set_defaults(func=action_format)


def action_format():
    print("format")
    for dir in format_src_dirs:
        command = (
            rf'find {dir} -type f \( -name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \)'
            r" -exec clang-format -style=file:.clang-format -i {} \;"
        )
        exec_shell_command(command)


def register_cmd_tidy():
    cmd = subparsers.add_parser(
        sub_command_tidy,
        help="tidy 检查",
    )
    cmd.set_defaults(func=action_tidy)


# TODO 查询当前目录下的扩展名列表集合
def action_tidy():
    action_rebuild_release()
    action_format()
    print("tidy")
    for dir in tidy_src_dirs:
        command = (
            rf'find {dir} -type f -name "*.cpp" -print0 | xargs -0 clang-tidy '
            r"--config-file=.clang-tidy -p=build/ninja-release --quiet"
        )
        if "include" == dir:
            command = (
                rf'find {dir} -type f -name "*.h" -print0 | xargs -0 clang-tidy '
                r"--config-file=.clang-tidy -p=build/ninja-release --quiet"
            )
        exec_shell_command(command)


if __name__ == "__main__":
    register_cmd_clean_release()
    register_cmd_clean_debug()
    register_cmd_build_release()
    register_cmd_build_debug()
    register_cmd_rebuild_release()
    register_cmd_rebuild_debug()
    register_cmd_run_release()
    register_cmd_run_debug()
    register_cmd_rerun_release()
    register_cmd_rerun_debug()
    register_cmd_run_test_common()
    register_cmd_rerun_test_common()
    register_cmd_coverage()
    register_cmd_doc()
    register_cmd_format()
    register_cmd_tidy()

    args = parser.parse_args()
    if hasattr(args, "func"):
        args.func()
